/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "NetFile.h"
#include <stddef.h>
#ifdef NETFILESYSTEM_URL
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif
#include <string>

namespace liba
{
namespace resource
{
		
NetFile::NetFile(const filesystem::String & name) : _fileData(NULL), _fileOffset(0), _fileSize(0)
{
#ifdef NETFILESYSTEM_URL
	// parse URL
	std::string inputURL = NETFILESYSTEM_URL + std::string(name.c_str());
	
	// try to find :// in URL
	int protocolPosition = inputURL.find("://", 0);
	
	// find address path slash
	int pathPosition     = inputURL.find("/", protocolPosition == inputURL.npos ? 0 : protocolPosition + 3);
	
	std::string host     = inputURL.substr(protocolPosition == inputURL.npos ? 0 : protocolPosition + 3, 
										   (pathPosition == inputURL.npos ? 0 : pathPosition) - (protocolPosition == inputURL.npos ? 0 : protocolPosition + 3));
	
	std::string path     = pathPosition == inputURL.npos ? "" : inputURL.substr(pathPosition);
	
	// resolve server IP address
	std::string serverIP = "";
	hostent * hostEntry = gethostbyname(host.c_str());
	if(hostEntry == NULL)
	{
		printf("NetFileSystem: Error resolving IP address for domain '%s' to read resource '%s'\n", host.c_str(), name.c_str());
		return;
	}
	else
	{
		in_addr * hostAddress = (struct in_addr*)hostEntry->h_addr_list[0];
		serverIP = inet_ntoa(*hostAddress);
	}
	
	// create server address structure
	struct sockaddr_in address;
	
	address.sin_family      = AF_INET;
	address.sin_addr.s_addr = inet_addr(serverIP.c_str());
	address.sin_port        = htons(80);
	
	// create socket
	int socketID = socket(AF_INET, SOCK_STREAM, 0);
	
	// connect to server
	if(connect(socketID, (struct sockaddr*)&address, sizeof(address)) == -1)
	{
		// close socket
		close(socketID);
		
		printf("NetFileSystem: Unable connect to host '%s' to read resource '%s'\n", host.c_str(), name.c_str());
		return;
	}
	
	// create request
	char requestBuffer[1024];
	sprintf(requestBuffer, "GET %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: Arello NetFileSystem\r\nConnection: close\r\n\r\n", path.c_str(), host.c_str());
	
	// send request
	int sent = send(socketID, requestBuffer, strlen(requestBuffer), 0);
	if(strlen(requestBuffer) != sent)
	{
		// close socket
		close(socketID);
		
		printf("NetFileSystem: Unable send request to server '%s' for resource '%s'\n", host.c_str(), name.c_str());
		return;
	}
	
	// buffer to read from socket
	const int readBufferSize = 16 * 1024;
	int       received       = 0;
	char      receiveBuffer[readBufferSize];
	
	// HTPP header buffer
	bool   parseHeader = true;
	int    headerSize  = 0;
	char * headerText  = NULL;
	
	// read server's answer
	while((received = recv(socketID, receiveBuffer, readBufferSize, 0)) != -1)
	{
		// if all data received
		if(received == 0)
			break;
		
		// if we read HTTP header now
		if(parseHeader)
		{
			// update header size
			int oldHeaderSize = headerSize;
			headerSize += received;
			
			// allocate new header buffer
			char * newHeader = new char[headerSize];
			
			// merge data from buffers
			if(headerText != NULL)
				memcpy(newHeader, headerText, oldHeaderSize);
			
			memcpy(&newHeader[oldHeaderSize], receiveBuffer, received);
			
			// free old buffer if exits
			if(headerText != NULL)
				delete [] headerText;
			
			// reset buffer
			headerText = newHeader;
			
			// check for end of header
			for(int i = 0; i < headerSize; i++)
			{
				// check for empty line - end of header
				if(memcmp(&headerText[i], "\r\n\r\n", 4) == 0)
				{
					// ok, we found end of header, now we need to check HTTP answer status
					if(memcmp(&headerText[9], "200", 3) == 0)
					{
						// all ok, HTTP 200 OK received
						// check if header comtains amy body data
						if(headerSize - i > 4)
						{
							// compute left bytes
							_fileSize = headerSize - i - 4;
							
							// allocate buffer
							_fileData = new unsigned char[_fileSize];
							
							// read bytes to buffer
							memcpy(_fileData, &headerText[i + 4], _fileSize);
						}
						
						// parse body now
						parseHeader = false;
						
						// free header buffer
						delete [] headerText;
						
						break;
					}
					else
					{
						// check for end of first header line
						for(int j = 0; j < headerSize; j++)
						{
							if(memcmp(&headerText[j], "\r\n", 2) == 0)
							{
								headerText[j] = '\0';
								break;
							}
						}
						
						// don't warn about 404
						if(memcmp(&headerText[9], "404", 3) != 0)
							printf("NetFileSystem: Unable to read resource '%s' from server '%s', HTTP response: '%s'\n", name.c_str(), host.c_str(), headerText);
						
						// delete buffer
						delete [] headerText;
						
						// close socket
						close(socketID);
						
						// exit
						return;
					}
				}
			}
		}
		else
		{
			// update buffer size
			int oldBufferSize = _fileSize;
			_fileSize += received;
			
			// allocate new buffer
			unsigned char * newBuffer = new unsigned char[_fileSize];
			
			// merge data from buffers
			if(_fileData != NULL)
				memcpy(newBuffer, _fileData, oldBufferSize);
			
			memcpy(&newBuffer[oldBufferSize], receiveBuffer, received);
			
			// free old buffer if exits
			if(_fileData != NULL)
				delete [] _fileData;
			
			// reset buffer
			_fileData = newBuffer;
		}
	}
	
	// check for error
	if(received < 0)
	{
		printf("NetFileSystem: Unable to read resource '%s' from server '%s'\n", name.c_str(), host.c_str());
		
		// free buffer if exists
		if(_fileData != NULL)
			delete [] _fileData;
		
		_fileData = NULL;
	}
	else
	{
		printf("NetFileSystem: Resource '%s' successfully readed from server '%s'\n", name.c_str(), host.c_str());
	}
	
	// close socket
	close(socketID);
#endif
}

NetFile::~NetFile()
{
	// destroy data if it exists
	if(_fileData != NULL)
		delete [] _fileData;
}

bool NetFile::is_valid() const
{
	// resource is valid if it have data
	return _fileData != NULL;
}

int NetFile::read(void * data, int count)
{
	// check if resource is valid
	if(!is_valid())
		return 0;
	
	// check available bytes count
	unsigned int availableBytes = _fileSize - _fileOffset;
	
	// exit if OEF reached
	if(availableBytes <= 0)
		return 0;
	
	// compute bytes to read from file
	unsigned int bytesToRead = availableBytes > count ? count : availableBytes;
	
	// read bytes to buffer
	memcpy(data, _fileData, bytesToRead);
	
	// update file offset
	_fileOffset += bytesToRead;
	
	// return readed bytes
	return bytesToRead;
}

int NetFile::skip(int count)
{
	// check if resource is valid
	if(!is_valid())
		return 0;
	
	// save old offset
	unsigned int oldOffset = _fileOffset;
	
	// update offset
	_fileOffset += count;
	
	// clamp offset
	if(_fileOffset < 0)
		_fileOffset = 0;
	
	if(_fileOffset > _fileSize)
		_fileOffset = _fileSize;
	
	// return difference between old and new offsets
	return _fileOffset - oldOffset;
}

int NetFile::get_size() const
{
	// check if resource is valid
	if(!is_valid())
		return 0;
	
	// return file size
	return _fileSize;
}

int NetFile::get_position() const
{
	// check if resource is valid
	if(!is_valid())
		return 0;
	
	// return current file offset
	return _fileOffset;
}

void NetFile::reset(int position)
{
	// check if resource is valid
	if(!is_valid())
		return;
	
	// sets new offset
	_fileOffset = position;
	
	// clamp offset
	if(_fileOffset < 0)
		_fileOffset = 0;
	
	if(_fileOffset > _fileSize)
		_fileOffset = _fileSize;
}
	
		
}
}